package pathfinding

import (
	"fmt"
	"sync"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/common/feature/orbit"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/serialize"
)

type RuleInstance interface {
	JPSInterface
	ZRInterface
}

type Rule struct {
	master bool
	*cluster.Client
	instance RuleInstance

	agent    cluster.Container
	service  *Service
	identity string

	mode   int
	notice sync.Pool

	Gap  orbit.Location
	Wrap orbit.Location
}

func NewRule(agent cluster.Container, service *Service, identity string, mode int) *Rule {
	instance := NewHexInstance(RowHexMode)
	r := &Rule{
		agent:    agent,
		service:  service,
		identity: identity,
		instance: instance,
		mode:     mode,
		notice:   sync.Pool{},
	}
	r.notice.New = func() interface{} {
		return make(chan struct{}, 1)
	}
	return r
}

func (rule *Rule) BindBlock(block []byte) {
	blockVector2 := DecodeVector2(block)
	rule.Gap = orbit.Location{
		X: orbit.Point(blockVector2.X),
		Y: orbit.Point(blockVector2.Y),
	}
	rule.Wrap = orbit.Location{
		X: orbit.Point(blockVector2.X),
		Y: orbit.Point(blockVector2.Y),
	}
}

func (rule *Rule) Init(data def.Serializer, fn func()) {
	client := rule.agent.Balance().GenerateClient(rule.service.ID(), rule.identity)
	rule.Client = client
	rule.On(OnInit, func(message asteroid.Message) {
		rule.master = true
		fn()
	})
	rule.Emit().Data(data).Send(Init)
}

func (rule *Rule) Stop() {
	if rule.master && rule.Client != nil {
		rule.Emit().Send(Stop)
	}
}

func (rule *Rule) ServiceExecute(event interface{}, params []interface{}) interface{} {
	var d interface{}
	c := rule.notice.Get().(chan struct{})
	rule.Emit().List(params...).CallbackTo(func(success bool, data interface{}) {
		//fmt.Println("path execute", success, data)
		d = data
		c <- struct{}{}
	}).Send(event)
	<-c
	rule.notice.Put(c)
	return d
}

func (rule *Rule) Location(position []byte) string {
	location := orbit.LocationCM.DecodeLocation(position)
	block := orbit.LocationCM.CenterBlockWithLocation(rule.Gap, rule.Wrap, location)
	sub := orbit.GetSubWithBlock(block)
	orbit.LocationCM.ReleaseBlock(block)
	orbit.LocationCM.ReleaseLocation(location)
	return sub
}

func (rule *Rule) Block() orbit.Location {
	return rule.Gap
}

func (rule *Rule) OnPathFinding(start []byte, end []byte, mask serialize.AutoIStruct, complete bool) [][]byte {
	var d [][]byte
	c := rule.notice.Get().(chan struct{})

	slot := serialize.GetSlot()
	_ = slot.Set(mask)

	pathData := PathDataPool.Get().(*PathData)
	pathData.Start = start
	pathData.End = end
	pathData.Complete = complete
	pathData.Data = slot.Bytes()

	rule.Emit().AutoRelease(slot, mask).Data(pathData).ResponseSerialize(func() def.OptimizeSerializer {
		return PositionsResponsePool.Get().(*PositionsResponse)
	}).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			positionsResponse := data.(*PositionsResponse)
			d = positionsResponse.Positions
			positionsResponse.Release()
		}

		if !success {
			fmt.Println("path", success)
		}
		//fmt.Println("path", success, d)
		//middle := time.Now()
		//fmt.Println("path1", middle, middle.Sub(startTime))
		c <- struct{}{}
	}).Send(Path)
	slot.Release()
	pathData.Release()
	<-c
	rule.notice.Put(c)
	//endTime := time.Now()
	//fmt.Println("path2", endTime, endTime.Sub(startTime))
	return d
}

func (rule *Rule) OnPlanFinding(start []byte, end []byte, mask serialize.AutoIStruct, complete bool) ([][]byte, [][]byte) {
	var d1 [][]byte
	var d2 [][]byte
	c := rule.notice.Get().(chan struct{})
	slot := serialize.GetSlot()
	_ = slot.Set(mask)

	planData := PlanDataPool.Get().(*PlanData)
	planData.Start = start
	planData.End = end
	planData.Complete = complete
	planData.Data = slot.Bytes()

	rule.Emit().AutoRelease(slot, mask).Data(planData).ResponseSerialize(func() def.OptimizeSerializer {
		return TwoPositionsResponsePool.Get().(*TwoPositionsResponse)
	}).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			twoPositionsResponse := data.(*TwoPositionsResponse)
			d1 = twoPositionsResponse.Positions1
			d2 = twoPositionsResponse.Positions2
			twoPositionsResponse.Release()
		}
		if !success {
			fmt.Println("plan", success)
		}
		//middle := time.Now()
		//fmt.Println("plan1", success, d)
		c <- struct{}{}
	}).Send(Plan)
	slot.Release()
	planData.Release()
	<-c
	rule.notice.Put(c)
	//endTime := time.Now()
	//fmt.Println("plan2", endTime, endTime.Sub(startTime))
	return d1, d2
}

func (rule *Rule) OnAutoFinding(start []byte, end []byte, timestamp int64, request serialize.AutoIStruct) [][]byte {
	var d [][]byte
	c := rule.notice.Get().(chan struct{})
	slot := serialize.GetSlot()
	_ = slot.Set(request)

	autoData := AutoDataPool.Get().(*AutoData)
	autoData.Start = start
	autoData.End = end
	autoData.Timestamp = timestamp
	autoData.Data = slot.Bytes()

	rule.Emit().AutoRelease(slot, request).Data(autoData).ResponseSerialize(func() def.OptimizeSerializer {
		return PositionsResponsePool.Get().(*PositionsResponse)
	}).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			positionsResponse := data.(*PositionsResponse)
			d = positionsResponse.Positions
			positionsResponse.Release()
		}
		if !success {
			fmt.Println("auto", success)
		}
		//middle := time.Now()
		//fmt.Println("auto1", success, d)
		c <- struct{}{}
	}).Send(Auto)
	slot.Release()
	autoData.Release()
	<-c
	rule.notice.Put(c)
	//endTime := time.Now()
	//fmt.Println("auto2", endTime, endTime.Sub(startTime))
	return d
}

func (rule *Rule) UpdateNode(position []byte, identity int, t byte) {
	updateNodeData := UpdateNodePool.Get().(*UpdateNodeData)
	updateNodeData.Position = position
	updateNodeData.Identity = identity
	updateNodeData.T = t
	rule.Emit().Data(updateNodeData).Send(UpdateNode)
	updateNodeData.Release()
}

func (rule *Rule) UpdateFixed(positions [][]byte, identity int, t byte, view byte, item []byte, timestamp int64) {
	updateFixedData := UpdateFixedPool.Get().(*UpdateFixedData)
	updateFixedData.Positions = positions
	updateFixedData.Identity = identity
	updateFixedData.T = t
	updateFixedData.View = view
	updateFixedData.Item = item
	updateFixedData.Timestamp = timestamp
	rule.Emit().Data(updateFixedData).Send(UpdateFixed)
	updateFixedData.Release()
}

func (rule *Rule) MoveSeat(item []byte, start []byte, end []byte, startTime int64, identity int, view byte, extend serialize.AutoIStruct) bool {
	var d bool
	c := rule.notice.Get().(chan struct{})
	slot := serialize.GetSlot()
	_ = slot.Set(extend)

	moveSeatData := MoveSeatPool.Get().(*MoveSeatData)
	moveSeatData.Item = item
	moveSeatData.Start = start
	moveSeatData.End = end
	moveSeatData.StartTime = startTime
	moveSeatData.Identity = identity
	moveSeatData.View = view
	moveSeatData.Extend = slot.Bytes()
	//fmt.Println("move start")
	rule.Emit().AutoRelease(extend).Data(moveSeatData).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			d = utils.ToBool(data)
		}
		if !success {
			fmt.Println("move", success)
		}
		c <- struct{}{}
	}).Send(MoveSeat)
	slot.Release()
	moveSeatData.Release()
	<-c
	rule.notice.Put(c)
	return d
}

func (rule *Rule) GetNode(position []byte) (int, byte, []byte) {
	var d1 int
	var d2 byte
	var d3 []byte
	c := rule.notice.Get().(chan struct{})
	getNodeData := GetNodePool.Get().(*GetNodeData)
	getNodeData.Position = position
	rule.Emit().Data(getNodeData).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			d := data.([]interface{})
			d1 = utils.ToInt(d[0])
			d2 = utils.ToByte(d[1])
			d3 = utils.ToBytes(d[2])
		}
		if !success {
			fmt.Println("nodeIdentity", success)
		}
		c <- struct{}{}
	}).Send(GetNode)
	getNodeData.Release()
	<-c
	rule.notice.Put(c)
	return d1, d2, d3
}

func (rule *Rule) GetItemList(positions [][]byte, timestamp int64) ([]byte, [][]byte, []int) {
	var d1 []byte
	var d2 [][]byte
	var d3 []int
	c := rule.notice.Get().(chan struct{})
	getItemListData := GetItemListPool.Get().(*GetItemListData)
	getItemListData.Positions = positions
	getItemListData.Timestamp = timestamp
	rule.Emit().Data(getItemListData).CallbackTo(func(success bool, data interface{}) {
		//fmt.Println("get item list", success, data)
		if success && data != nil {
			d := data.([]interface{})
			d1 = utils.ToBytes(d[0])
			d2 = utils.ToBytesSlice(d[1])
			d3 = utils.ToIntSlice(d[2])
		}
		if !success {
			fmt.Println("get item list", success)
		}
		c <- struct{}{}
	}).Send(GetItemList)
	getItemListData.Release()
	<-c
	rule.notice.Put(c)
	return d1, d2, d3
}
func (rule *Rule) GetFixedList(positions [][]byte) ([][]byte, []int) {
	var d1 [][]byte
	var d2 []int
	c := rule.notice.Get().(chan struct{})
	getFixedListData := GetFixedListPool.Get().(*GetFixedListData)
	getFixedListData.Positions = positions
	rule.Emit().Data(getFixedListData).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			//fmt.Println("get item list", data)
			d := data.([]interface{})
			d1 = utils.ToBytesSlice(d[0])
			d2 = utils.ToIntSlice(d[1])
		}
		if !success {
			fmt.Println("get fixed list", success)
		}
		c <- struct{}{}
	}).Send(GetFixedList)
	getFixedListData.Release()
	<-c
	rule.notice.Put(c)
	return d1, d2
}
func (rule *Rule) GetItem(position []byte, timestamp int64) (byte, []byte, int) {
	var d1 byte
	var d2 []byte
	var d3 int

	c := rule.notice.Get().(chan struct{})
	getItemData := GetItemPool.Get().(*GetItemData)
	getItemData.Position = position
	getItemData.Timestamp = timestamp

	rule.Emit().Data(getItemData).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			//fmt.Println("get item list", data)
			d := data.([]interface{})
			d1 = utils.ToByte(d[0])
			d2 = utils.ToBytes(d[1])
			d3 = utils.ToInt(d[2])
		}
		if !success {
			fmt.Println("get item", success)
		}
		c <- struct{}{}
	}).Send(GetItem)
	getItemData.Release()
	<-c
	rule.notice.Put(c)
	return d1, d2, d3
}

func (rule *Rule) CanAndPreMove(start []byte, end []byte, timestamp int64, request serialize.AutoIStruct) []byte {
	var d1 []byte
	c := rule.notice.Get().(chan struct{})
	slot := serialize.GetSlot()
	_ = slot.Set(request)

	canAndPreMoveData := CanAndPreMovePool.Get().(*CanAndPreMoveData)
	canAndPreMoveData.Start = start
	canAndPreMoveData.End = end
	canAndPreMoveData.Timestamp = timestamp
	canAndPreMoveData.Data = slot.Bytes()

	rule.Emit().AutoRelease(slot, request).Data(canAndPreMoveData).ResponseData().CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			d1 = data.([]byte)
		}
		if !success {
			fmt.Println("can and pre move", success)
		}
		c <- struct{}{}
	}).Send(CanAndPreMove)
	slot.Release()
	canAndPreMoveData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}

func (rule *Rule) CanAndPreRange(position []byte, distance int, timestamp int64, requests []serialize.AutoIStruct) [][]byte {
	var d1 [][]byte
	c := rule.notice.Get().(chan struct{})

	canAndPreRangeData := CanAndPreRangePool.Get().(*CanAndPreRangeData)
	canAndPreRangeData.Position = position
	canAndPreRangeData.Distance = distance
	canAndPreRangeData.Timestamp = timestamp

	pipe := rule.Emit()
	ll := [][]byte{}
	for _, request := range requests {
		slot := serialize.GetSlot()
		_ = slot.Set(request)
		pipe.AutoRelease(slot, request)
		ll = append(ll, slot.Bytes())
		slot.Release()
	}
	canAndPreRangeData.List = ll

	pipe.Data(canAndPreRangeData).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			d1 = data.([][]byte)
		}
		if !success {
			fmt.Println("can and pre range", success)
		}
		c <- struct{}{}
	}).Send(CanAndPreRange)
	canAndPreRangeData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}

func (rule *Rule) RandomPosition(canType int, canFix bool, canSeat bool, timestamp int64) []byte {
	var d1 []byte
	c := rule.notice.Get().(chan struct{})

	randomPositionData := RandomPositionPool.Get().(*RandomPositionData)
	randomPositionData.CanType = canType
	randomPositionData.CanFix = canFix
	randomPositionData.CanSeat = canSeat
	randomPositionData.Timestamp = timestamp

	rule.Emit().Data(randomPositionData).ResponseData().CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			d1 = data.([]byte)
		}
		if !success {
			fmt.Println("can and pre range", success)
		}
		c <- struct{}{}
	}).Send(RandomPosition)
	randomPositionData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}
func (rule *Rule) GetLinksBy(relation byte, position []byte) [][]byte {
	var d [][]byte
	c := rule.notice.Get().(chan struct{})
	getLinksByData := GetLinksByPool.Get().(*GetLinksByData)
	getLinksByData.Relation = relation
	getLinksByData.Position = position
	rule.Emit().Data(getLinksByData).ResponseSerialize(func() def.OptimizeSerializer {
		return PositionsResponsePool.Get().(*PositionsResponse)
	}).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			positionsResponse := data.(*PositionsResponse)
			d = positionsResponse.Positions
			positionsResponse.Release()
		}
		if !success {
			fmt.Println("get links", success)
		}
		c <- struct{}{}
	}).Send(GetLinksBy)
	getLinksByData.Release()
	<-c
	rule.notice.Put(c)
	return d
}
func (rule *Rule) GetLinks(position []byte) RelationLinkManagerSlice {
	var d1 RelationLinkManagerSlice
	c := rule.notice.Get().(chan struct{})

	getLinksByData := GetLinksByPool.Get().(*GetLinksByData)
	getLinksByData.Position = position

	rule.Emit().Data(getLinksByData).ResponseSerialize(func() def.OptimizeSerializer {
		return serialize.GetSlot()
	}).CallbackTo(func(success bool, data interface{}) {
		if success && data != nil {
			//fmt.Println("get item list", data)
			slot := data.(*serialize.Mps)
			d1 = RelationLinkManagerSlice{}
			if err := slot.Set(d1); err != nil {
				fmt.Println("getLinks err", err)
			}
		}
		if !success {
			fmt.Println("get links", success)
		}
		c <- struct{}{}
	}).Send(GetLinks)
	getLinksByData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}
func (rule *Rule) AddLinks(item []byte, relation byte, positions [][]byte) bool {
	var d1 bool
	c := rule.notice.Get().(chan struct{})
	addLinksData := AddLinksPool.Get().(*AddLinksData)
	addLinksData.Item = item
	addLinksData.Relation = relation
	addLinksData.Positions = positions
	rule.Emit().Data(addLinksData).CallbackTo(func(success bool, data interface{}) {
		if !success {
			fmt.Println("add links", success)
		} else {
			d1 = utils.ToBool(data)
		}
		c <- struct{}{}
	}).Send(AddLinks)
	addLinksData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}
func (rule *Rule) RemoveLinks(item []byte, relation byte, positions [][]byte) bool {
	var d1 bool
	c := rule.notice.Get().(chan struct{})
	removeLinksData := RemoveLinksPool.Get().(*RemoveLinksData)
	removeLinksData.Item = item
	removeLinksData.Relation = relation
	removeLinksData.Positions = positions
	rule.Emit().Data(removeLinksData).CallbackTo(func(success bool, data interface{}) {
		if !success {
			fmt.Println("remove links", success)
		} else {
			d1 = utils.ToBool(data)
		}
		c <- struct{}{}
	}).Send(RemoveLinks)
	removeLinksData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}

func (rule *Rule) HasView(start []byte, target []byte, identity int, distance int, timestamp int64) bool {
	var d1 bool
	c := rule.notice.Get().(chan struct{})
	hasViewData := HasViewPool.Get().(*HasViewData)
	hasViewData.Start = start
	hasViewData.Target = target
	hasViewData.Identity = identity
	hasViewData.Distance = distance
	hasViewData.Timestamp = timestamp
	hasViewData.Self = true
	rule.Emit().Data(hasViewData).CallbackTo(func(success bool, data interface{}) {
		if !success {
			fmt.Println("has views self", success)
		} else {
			d1 = utils.ToBool(data)
		}
		c <- struct{}{}
	}).Send(HasView)
	hasViewData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}
func (rule *Rule) HasViewOther(start []byte, target []byte, identity int, distance int, timestamp int64) bool {
	var d1 bool
	c := rule.notice.Get().(chan struct{})
	hasViewData := HasViewPool.Get().(*HasViewData)
	hasViewData.Start = start
	hasViewData.Target = target
	hasViewData.Identity = identity
	hasViewData.Distance = distance
	hasViewData.Timestamp = timestamp
	hasViewData.Self = true
	rule.Emit().Data(hasViewData).CallbackTo(func(success bool, data interface{}) {
		if !success {
			fmt.Println("has views", success)
		} else {
			d1 = utils.ToBool(data)
		}
		c <- struct{}{}
	}).Send(HasView)
	hasViewData.Release()
	<-c
	rule.notice.Put(c)
	return d1
}
